package viper.receiver;

import java.util.Timer;
import java.util.TimerTask;
import java.io.IOException;

import javax.media.Manager;
import javax.media.Player;
import javax.media.Time;
import javax.media.protocol.PushBufferDataSource;
import javax.media.protocol.PushBufferStream;


import viper.call.common.LiveStream;
import viper.main.StateManager;
import viper.main.ViperHeader;

public class CallReceiver implements Runnable {

	public final static CallReceiver INSTANCE = new CallReceiver();
	private CallReceiver(){

	}

	private LiveStream rxls = new LiveStream();
	//private byte[] buff1 = null; //Storage for VHeader = 1 type packets
	//	private byte[] buff3; //Storage for VHeader = 3 type packets
	//	private byte[] buffrecovered=null; //also VHeader = 2

	private Player player = null;
	
	//Sliding window parameters---------------------
	private static final int BIG_BUFFER_SIZE = 256; //1 byte for sequence number
	private static final int WINDOW_SIZE = 20; //Can be tweaked
	private int win_start = 0;  
	private byte[][][] big_buffer = null;
	public static final int MAX_PKT_SZ = 9000; //replace w/ Ji's
	
	//Timer parameters--------------------------------
	private Timer timer = null;
	private int timeout_in_millis; //depends on what code
	private TimerTask timerTask = null;
	private static final int INITIAL_DELAY = 500;
	
	private boolean gotFirstPacket = false;
	
	
	public void run() {

		//DataSource source = null;
		//PushBufferStream pbs = null;
		
		try {		
			// Construct a new socket on specified port.
			// All packets received on this port will be assumed to be VIPER packets.

			// Construct the custom PushBufferStream

			// Initialize receive stream with DataSource
			CustomDataSource rxcds = new CustomDataSource(rxls);
			rxcds.connect(); 

			// Construct and start the audio player
			player = Manager.createRealizedPlayer(rxcds);
			player.start();
			System.out.println("Starting player...");
		} catch (Throwable t) {
			t.printStackTrace();
		}
		
		//Initialize big_buffer and timeout depending on div. code
		byte divCode = StateManager.INSTANCE.getDiversityCode();
		if(divCode == ViperHeader.CODE_232)
		{
			big_buffer = new byte[BIG_BUFFER_SIZE][3][MAX_PKT_SZ];
			timeout_in_millis = 32;
		}
		else if(divCode == ViperHeader.CODE_364)
		{
			big_buffer = new byte[BIG_BUFFER_SIZE][3][];
			timeout_in_millis = 48;
		}
		else if(divCode == ViperHeader.CODE_101)
		{
			big_buffer = new byte[BIG_BUFFER_SIZE][1][MAX_PKT_SZ];
			System.out.println("Initializing sliding win for (1,0,1) case"); //for troubleshooting
			timeout_in_millis = 500; //16
		}
		else if(divCode == ViperHeader.CODE_111)
		{
			big_buffer = new byte[BIG_BUFFER_SIZE][1][];
			timeout_in_millis = 48;
		}
			

		timer = new Timer();
		timerTask = new ViperTimerTask();
	
		

		//Run Sliding Window Thread which will run continuously in background while program is IN_CALL 
		//to manage the big_buffer, i.e. advance sliding window and do de-diversification
		//StateManager.INSTANCE.getPool().execute(new SlidingWinThread());
		
		timer.schedule(timerTask, INITIAL_DELAY); //Tweak delay parameter based on how long it takes to send 1st pkt
		System.out.println("run CallReceiver");
	}

	public void parsePacket(byte[] buff) {
		

		
		/* Format of byte[] buff
		 * <Seq # (1 byte)> <Pkt type (1 byte)> <Data>
		 */
		int seqNum = buff[0] & 0xFF;
		
		
		if(!gotFirstPacket)
		{
			gotFirstPacket = true;
			win_start = seqNum; 
			System.out.println("First packet: " + win_start);
		}
		
		//System.out.println("seqNum = " + seqNum);

		
		//byte pktType = buff[1]; //TODO: make it 0, 1, ...
		int pktType = 0;
		
		byte[] data = new byte[buff.length - 2]; //TODO: make method?
		System.arraycopy(buff, 2, data, 0, data.length);
		
		if(!isInWindowRange(seqNum))
			return; //drop packet
		
		
		//System.out.println("in range");
		
		big_buffer[seqNum][pktType] = data; //place in buffer
		
		
		if(seqNum==win_start)
		{
			byte[][] dediv = dediversify(false, big_buffer[seqNum]);
			if(dediv!=null)
			{
				for(byte[] b : dediv)
				{
					rxls.update(b);
				}
				shiftWindow();
			}
			
		}
	}
	
	//TODO: replace w/ Joe's -- this is just temporary to test 1,0,1
	private byte[][] dediversify(boolean partial, byte[][] bs) {
		//System.out.println("dediv" + partial);
		return bs;
	}

	private boolean isInWindowRange(int seqNum)
	{
		if(win_start<=BIG_BUFFER_SIZE-WINDOW_SIZE)
		{
			if(seqNum>=win_start && seqNum<=win_start+WINDOW_SIZE)
				return true;
			else
				return false;
		}
		else
		{
			int win_end = BIG_BUFFER_SIZE - win_start + WINDOW_SIZE - 1;
			if(seqNum>=win_start || seqNum<=win_end)
				return true;
			else
				return false;
		}
	}
	
	private void shiftWindow()
	{
		//clear old row
//		for(byte[] b : big_buffer[win_start])
//		{
//			b = new byte[]{};
//		}
		win_start = incrementRowNumber(win_start);
		timerTask.cancel();
		timerTask = new ViperTimerTask();
		timer.schedule(timerTask, timeout_in_millis);
		
	}

	private int incrementRowNumber(int rowNumber)
	{
		if(rowNumber==BIG_BUFFER_SIZE-1)
			return 0;
		else
			return rowNumber + 1;
	}
	
	public void end()
	{
		if(player!=null)
			player.stop();
		gotFirstPacket = false;
		System.out.println("stop CallReceiver");
	}

	class CustomDataSource extends PushBufferDataSource {

		protected Object [] controls = new Object[0];
		protected boolean started = false;
		protected String contentType = "raw";
		protected boolean connected = false;
		protected Time duration = DURATION_UNKNOWN;
		protected LiveStream [] streams = null;
		protected LiveStream stream = null;

		public CustomDataSource(LiveStream ls) {
			streams = new LiveStream[1];
			streams[0] = ls;
			stream = ls;
		}

		public String getContentType() {
			if (!connected){
				System.err.println("Error: DataSource not connected");
				return null;
			}
			return contentType;
		}

		public void connect() throws IOException {
			if (connected)
				return;
			connected = true;
		}

		public void disconnect() {
			try {
				if (started)
					stop();
			} catch (IOException e) {}
			connected = false;
		}

		public void start() throws IOException {
			// we need to throw error if connect() has not been called
			if (!connected)
				throw new java.lang.Error("DataSource must be connected before it can be started");
			if (started)
				return;
			started = true;
			stream.start(true);
		}

		public void stop() throws IOException {
			if ((!connected) || (!started))
				return;
			started = false;
			stream.start(false);
		}

		public Object [] getControls() {
			return controls;
		}

		public Object getControl(String controlType) {
			try {
				Class<?>  cls = Class.forName(controlType);
				Object cs[] = getControls();
				for (int i = 0; i < cs.length; i++) {
					if (cls.isInstance(cs[i]))
						return cs[i];
				}
				return null;

			} catch (Exception e) {   // no such controlType or such control
				return null;
			}
		}

		public Time getDuration() {
			return duration;
		}

		public PushBufferStream [] getStreams() {
			if (streams == null) {
				streams = new LiveStream[1];
				stream = streams[0] = new LiveStream();
			}
			return streams;
		}
	}
	
	/**
	 * 
	 * @author Grace
	 * Sources:
	 * http://java.sun.com/products/jfc/tsc/articles/timer/
	 * http://java.sun.com/j2se/1.5.0/docs/api/java/util/TimerTask.html
	 */
	class ViperTimerTask extends TimerTask{
		
		public void run() {			
			byte[][] dediv = dediversify(true, big_buffer[win_start]);
			for(byte[] b : dediv)
			{
				rxls.update(b);
			}
			shiftWindow();
		}
	}
}